﻿' ******************************************************************************
' ** 
' **  Yahoo! Managed
' **  Written by Marius Häusler 2011
' **  It would be pleasant, if you contact me when you are using this code.
' **  Contact: YahooFinanceManaged@gmail.com
' **  Project Home: http://code.google.com/p/yahoo-finance-managed/
' **  
' ******************************************************************************
' **  
' **  Copyright 2011 Marius Häusler
' **  
' **  Licensed under the Apache License, Version 2.0 (the "License");
' **  you may not use this file except in compliance with the License.
' **  You may obtain a copy of the License at
' **  
' **    http://www.apache.org/licenses/LICENSE-2.0
' **  
' **  Unless required by applicable law or agreed to in writing, software
' **  distributed under the License is distributed on an "AS IS" BASIS,
' **  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
' **  See the License for the specific language governing permissions and
' **  limitations under the License.
' ** 
' ******************************************************************************


Namespace Base

    Friend Class TimeoutWebClient
        Implements IDisposable

        Public Event AsyncDownloadCompleted(ByVal sender As TimeoutWebClient, ByVal ea As StreamDownloadCompletedEventArgs)

        Private Delegate Sub AsyncCompletedDelegate(ByVal e As StreamDownloadCompletedEventArgs)
        Private mAsyncCompletedDel As AsyncCompletedDelegate = Nothing

        Private ReadOnly mHelper As New YahooManaged.MyHelper
        Private mGui As System.Windows.Forms.Control = Nothing
        Private mActualDownload As HttpWebRequest = Nothing

        Private mTimeout As Integer = 0
        Private mProxy As IWebProxy = Nothing
        Private mUserArgs As Object = Nothing

        Public ReadOnly Property UserArgs() As Object
            Get
                Return mUserArgs
            End Get
        End Property
        Public Property Timeout() As Integer
            Get
                Return mTimeout
            End Get
            Set(ByVal value As Integer)
                mTimeout = value
            End Set
        End Property
        Public Property Proxy() As IWebProxy
            Get
                Return mProxy
            End Get
            Set(ByVal value As IWebProxy)
                mProxy = value
            End Set
        End Property
        Public ReadOnly Property IsBusy() As Boolean
            Get
                Return Me.ActualDownload IsNot Nothing
            End Get
        End Property
        Friend ReadOnly Property ActualDownload() As HttpWebRequest
            Get
                Return mActualDownload
            End Get
        End Property

        Public Sub New(ByVal gui As System.Windows.Forms.Control, ByVal timeout As Integer)
            mProxy = WebRequest.DefaultWebProxy
            mTimeout = timeout
            mGui = gui
            mAsyncCompletedDel = New AsyncCompletedDelegate(AddressOf Me.AsyncCompleted)
        End Sub

        Public Sub CancelAsync()
            Try
                If Me.ActualDownload IsNot Nothing Then Me.ActualDownload.Abort()
            Catch ex As ArgumentOutOfRangeException
                Me.Dispose()
            End Try
        End Sub

        Public Function Download(ByVal url As String, Optional ByVal additionalHeaders() As KeyValuePair(Of HttpRequestHeader, String) = Nothing) As StreamResponse
            If Not mDisposedValue Then
                Me.CancelAsync()

                Dim startTime As DateTime = Date.Now
                Dim endTime As DateTime = Date.Now
                Dim memStream As IO.MemoryStream = Nothing
                Dim dlException As Net.WebException = Nothing
                Dim timeout As Integer = mTimeout
                Dim size As Integer = 0

                Try
                    Dim wr As HttpWebRequest = DirectCast(HttpWebRequest.Create(url), HttpWebRequest)
                    If mProxy IsNot Nothing Then wr.Proxy = mProxy
                    wr.Method = "GET"
                    If additionalHeaders IsNot Nothing Then
                        For Each header In additionalHeaders
                            wr.Headers.Add(header.Key, header.Value)
                        Next
                    End If
                    wr.Timeout = timeout
                    mActualDownload = wr

                    Using resp As HttpWebResponse = DirectCast(wr.GetResponse(), HttpWebResponse)
                        Dim s As IO.Stream = resp.GetResponseStream()
                        endTime = Date.Now
                        memStream = Me.CopyToMemoryStream(s)
                        mHelper.CloseStream(s)
                    End Using

                    If memStream IsNot Nothing AndAlso memStream.CanSeek Then Integer.TryParse(memStream.Length.ToString, size)
                Catch ex As Exception
                    dlException = Me.GetOrCreateWebException(ex, Nothing)
                Finally
                    mActualDownload = Nothing
                End Try

                Return New StreamResponse(New ConnectionInfo(dlException, timeout, size, startTime, endTime), memStream)
            Else
                Return Nothing
            End If
        End Function

        Public Sub DownloadAsync(ByVal url As String, ByVal userArgs As Object, Optional ByVal additionalHeaders() As KeyValuePair(Of HttpRequestHeader, String) = Nothing)
            If Not mDisposedValue Then
                mUserArgs = userArgs

                Dim startTime As DateTime = Date.Now
                Dim timeout As Integer = mTimeout

                Try
                    Dim wr As HttpWebRequest = DirectCast(HttpWebRequest.Create(url), HttpWebRequest)
                    If mProxy IsNot Nothing Then wr.Proxy = mProxy
                    wr.Method = "GET"
                    If additionalHeaders IsNot Nothing Then
                        For Each header In additionalHeaders
                            wr.Headers.Add(header.Key, header.Value)
                        Next
                    End If
                    wr.Timeout = timeout
                    mActualDownload = wr

                    Dim asyncArgs As New AsyncDownloadArgs(userArgs, Date.Now, wr, False)

                    Dim res As IAsyncResult = wr.BeginGetResponse(New AsyncCallback(AddressOf Me.RequestCompleted), asyncArgs)
                    Try
                        System.Threading.ThreadPool.RegisterWaitForSingleObject(res.AsyncWaitHandle, New System.Threading.WaitOrTimerCallback(AddressOf Me.RequestTimeout), asyncArgs, (timeout), True)
                    Catch ex As NotSupportedException
                    End Try

                Catch ex As Exception
                    Dim dlException As Net.WebException = Me.GetOrCreateWebException(ex, Nothing)
                    Me.AsyncCompleted(New StreamDownloadCompletedEventArgs(userArgs, New StreamResponse(New ConnectionInfo(dlException, timeout, 0, startTime, Date.Now), Nothing)))
                End Try
            End If
        End Sub

        Private Sub RequestTimeout(ByVal state As Object, ByVal timedOut As Boolean)
            If timedOut Then
                Dim asyncArgs As AsyncDownloadArgs = CType(state, AsyncDownloadArgs)
                asyncArgs.TimedOut = True
                asyncArgs.WR.Abort()
            End If
        End Sub
        Private Sub RequestCompleted(ByVal result As IAsyncResult)
            If Not mDisposedValue Then
                Dim asyncArgs As AsyncDownloadArgs = CType(result.AsyncState, AsyncDownloadArgs)
                Dim memStream As System.IO.MemoryStream = Nothing
                Dim dlException As WebException = Nothing
                Dim endTime As Date = Date.Now
                Dim size As Integer = 0
                mActualDownload = Nothing

                Try
                    Dim resp As HttpWebResponse = CType(asyncArgs.WR.EndGetResponse(result), HttpWebResponse)
                    If Not asyncArgs.TimedOut Then
                        Try
                            Dim s As System.IO.Stream = resp.GetResponseStream()
                            endTime = Date.Now
                            memStream = Me.CopyToMemoryStream(s)
                            mHelper.CloseStream(s)
                        Catch ex As Exception
                            dlException = Me.GetOrCreateWebException(ex, resp)
                        Finally
                            resp.Close()
                        End Try
                    Else
                        dlException = New WebException("Timeout Error.", Nothing, WebExceptionStatus.Timeout, resp)
                    End If

                Catch ex As Exception
                    dlException = Me.GetOrCreateWebException(ex, Nothing)
                Finally
                    If memStream IsNot Nothing AndAlso memStream.CanSeek Then Integer.TryParse(memStream.Length.ToString, size)
                    mUserArgs = Nothing

                    Me.AsyncCompleted(New StreamDownloadCompletedEventArgs(asyncArgs.UserArgs, New StreamResponse(New ConnectionInfo(dlException, asyncArgs.WR.Timeout, size, asyncArgs.StartTime, endTime), memStream)))
                End Try
            End If
        End Sub
        Private Sub AsyncCompleted(ByVal e As StreamDownloadCompletedEventArgs)
            If mGui IsNot Nothing AndAlso mGui.InvokeRequired Then : mGui.Invoke(mAsyncCompletedDel, e)
            Else : RaiseEvent AsyncDownloadCompleted(Me, e)
            End If
        End Sub

        Private Function GetOrCreateWebException(ByVal ex As Exception, ByVal resp As WebResponse) As WebException
            If TypeOf ex Is Net.WebException Then
                Return DirectCast(ex, Net.WebException)
            Else
                Return New Net.WebException("A Non-Net.WebException was throwed during download process. See [InnerException] for more details.", ex, Net.WebExceptionStatus.UnknownError, resp)
            End If
        End Function
        Private Function CopyToMemoryStream(ByVal source As IO.Stream) As IO.MemoryStream
            Dim ms As New IO.MemoryStream
            If source.CanRead Then
                Dim buffer As Byte() = New Byte(CInt(2048)) {}
                While True
                    Dim read As Integer = source.Read(buffer, 0, buffer.Length)
                    If read <= 0 Then
                        Exit While
                    End If
                    ms.Write(buffer, 0, read)
                End While
            End If
            ms.Position = 0
            Return ms
        End Function

#Region "IDisposable"
        Private mDisposedValue As Boolean = False
        Protected Overridable Sub Dispose(ByVal disposing As Boolean)
            If Not mDisposedValue Then
                mDisposedValue = True
                If disposing Then
                End If
                mGui = Nothing
                mProxy = Nothing
                mTimeout = 0
                mUserArgs = Nothing
                mActualDownload = Nothing
            End If
        End Sub
        Public Sub Dispose() Implements IDisposable.Dispose
            Dispose(True)
            GC.SuppressFinalize(Me)
        End Sub
#End Region

        Private Class AsyncDownloadArgs
            Inherits DownloadEventArgs
            Public StartTime As DateTime
            Public WR As HttpWebRequest = Nothing
            Public TimedOut As Boolean = False
            Public Sub New(ByVal userArgs As Object, ByVal st As DateTime, ByVal wr As HttpWebRequest, ByVal isTime As Boolean)
                MyBase.New(userArgs)
                Me.StartTime = st
                Me.WR = wr
                Me.TimedOut = isTime
            End Sub
        End Class
    End Class





End Namespace
